package core;

import java.util.ArrayList;
import java.util.List;

import persistence.TransactionManager;
import persistence.dao.IContainerToProductsDAO;
import persistence.dao.IProductGroupsDAO;
import persistence.dao.IProductsDAO;
import persistence.dto.ProductGroupDTO;

import core.reports.IHost;
import core.reports.IVisitor;
import core.reports.TraversalOrder;

/**
 * This class describes the ProductContainer.  This is the super
 * class for any object that holds products (Storage Unit and ProductGroup).
 * 
 * This is an abstract class because there are no objects called 
 * 'ProductContainer' but other objects are 'ProductContainer's.
 * 
 * @author Spencer Hoffa
 *
 */

public abstract class ProductContainer extends ModelObject implements Comparable, IHost
{
	/** Serializable ID
	 * 
	 */
	private static final long serialVersionUID = 4316255616281593649L;

	/** The name of this product container */
	protected String name;
	
	/** List of all children product groups */
	protected List<ProductGroup> groups;
	
	private int PCID;
	
	public void setPCID(int ID)
	{
		PCID = ID;
	}
	
	
	/**
	 *	This is the default constructor for this class. It 
	 *	contains no parameters.
	 */
	public ProductContainer()
	{
		
	}
	
	/**
	 * Constructor for Product Container. 
	 * 
	 * @param name The name of this product container.
	 */
	public ProductContainer(String name)
	{
		
	}
	
	/**
	 * This sets the name of this container.
	 * 
	 * @param newName This is a string representing the new 
	 * 		name for this container. Cannot be empty.
	 * 
	 * @throws IllegalArgumentException Throws exception when it's an invalid name.
	 */
	public void setName(String newName) throws IllegalArgumentException
	{
		assert(newName != "" && newName != null);
		
		if(newName == "" || newName == null)
			throw new IllegalArgumentException("newName was not a valid string");
		
		this.name = newName;
	}
	
	/**
	 * Returns the name of this container.
	 * 
	 * @return Returns a string with the container's name.
	 */
	public String getName()
	{
		return name;
	}
	
	/**
	 * Adds a product group to this container.
	 * 
	 * @param toAdd The Product group to be added.
	 * @return returns the toAdd ProductGroup upon success.
	 */
	public ProductGroup addProductGroup(ProductGroup toAdd) throws IllegalArgumentException
	{
		assert(toAdd != null);
		assert(this.canAddProductGroup(toAdd));
		if(toAdd == null)
			throw new IllegalArgumentException();
		
		if(this.canAddProductGroup(toAdd))
		{
			groups.add(toAdd);
			return toAdd;
		}
		else
			throw new IllegalArgumentException();
	}
	
	/**
	 * Check to see if it is legal to add this ProductGroup
	 * to this container.
	 * 
	 * @param toAdd The ProductGroup to add.
	 * @return Returns true if the ProductGroup can be added
	 * 		to this container. False if you cannot add it
	 * 		to this container.
	 */
	public boolean canAddProductGroup(ProductGroup toAdd)
	{
		assert(toAdd !=null);
		// this tests the unique constraint of ProductGroup names at 
		// the same level and non-null constraint
		if(toAdd.getName().compareTo("")==0)
			return false;
		/* need to check if the toAdd ProductGroup has the same parent set as "this" variable
		 * if not, then we return false
		 */
		if(!(toAdd.getParentContainer().equals(this)))
			return false;
		return !(containsProductGroup(toAdd));
	}
	
	/**
	 * Check if the ProductGroup is already contained in this
	 * container. 
	 * 
	 * @param pgName The name of the ProductGroup to check for.
	 * @return Returns true if the ProductGroup is inside this
	 * 		container.
	 * @throws NullPointerException if pg is null
	 */
	public boolean containsProductGroup(ProductGroup pg) throws NullPointerException
	{
		assert (pg!=null);
		if(pg==null)
			throw new NullPointerException();
		return groups.contains(pg);
	}
	
	
	/** used by both ProductGroup and StorageUnit to determine
	 * if calling object is equal to the obj object passed int
	 * 
	 * @param obj ProductContainer to be compared
	 * @return True if they are equal, false otherwise
	 */
	public boolean equals(Object obj)
    {
		assert(obj!=null);
    	if (obj == null) return false;
    	if (obj == this) return true;
    	if (obj.getClass() != this.getClass()) return false;
    	
    	
    	
    	if(obj instanceof ProductGroup)
    	{
    		ProductGroup group = (ProductGroup) obj;
        	ProductGroup thisGroup = (ProductGroup) this;
        	/* they are equal ProductGroups if they 
        	 * 1. have the same name
        	 * 2. have the same parent container
        	 */
        	return (group.getName().compareTo(thisGroup.getName())==0) 
        			&& (group.getParentContainer().equals(thisGroup.getParentContainer()));
    	}
    	else // obj  is instanceof StorageUnit
    	{
    		StorageUnit unit = (StorageUnit) obj;
    		StorageUnit thisUnit = (StorageUnit)this;
    		/* StorageUnits are the same if they:
    		 * 1. have the same name
    		 * 
    		 * since they are always children of the root, this is the
    		 * only check needed
    		 */
    		return (unit.getName().compareTo(thisUnit.getName())==0);
    	}
    	
    }
	
	  /**
     * hash code function
     * @return hash value
     */
	@Override 
    public int hashCode()
    {
    	    	
    	return Integer.toString(PCID).hashCode();
    	
    }
    
	
	/**
	 * Remove the ProductGroup from this container.
	 * 
	 * @param pgName The name of the ProductGroup to remove.
	 * @return Returns true if the ProductGroup was successfully
	 * 		removed. Otherwise it returns false.
	 */
	public boolean deleteProductGroup(ProductGroup pg)
	{
		assert(pg !=null);
		assert(canDeleteProductGroup(pg));
		if(!(canDeleteProductGroup(pg)))
			return false;
		TransactionManager.begin();
		IProductGroupsDAO dao = CoreFacade.getInstance().getFactory().getPGsDAO();
		IContainerToProductsDAO dao2 = CoreFacade.getInstance().getFactory().getContsToProdsDAO();
		ProductGroupDTO dto = pg.createDTO();
		boolean result = dao.remove(dto); 
		dao2.remove(pg.createDTO());
		TransactionManager.end(result);
		// if we succeeded in removing from the DB, then 
		if(result)
			groups.remove(pg);// remove method returns true upon success
		return result;
	}
	
	/**
	 * Check to see if you are allowed to remove this 
	 * ProductGroup. 
	 * 
	 * @param toRemove The ProductGroup to check if it is valid
	 * 		to remove.
	 * @return Returns true if you can remove.  False if you can't.
	 */
	public boolean canDeleteProductGroup(ProductGroup toRemove)
	{
		assert(toRemove != null);
		if(toRemove == null)
			return false;
		
		if(this.groups.contains(toRemove)) // it is in the list
		{
			// this method needs to see mappings of ProductGroups to items and products
			// in order to determine if it can remove items, otherwise I can't determine if
			// removing an item is acceptable, so for now it will just remove the item
			return true;
		}
		return false;
		
	}
	
	/**
	 * Deprecated, DO NOT USE !!!!
	 * I've determined that this method can't be performed by the GUI, so why are we implementing it
	 * -------
	 * This moves a productGroup from one container to another container.
	 * 
	 * @param toMove The ProductGroup to move
	 * @param from The ProductContainer to move from.
	 * @param to the ProductContainer to move to.
	 * @throws Exception 
	 * @throws IllegalArgument Throws if the ProductGroup, or ProductContainers do
	 * 		not exist.
	 */
	public void moveProductGroup(ProductGroup toMove, ProductContainer from, ProductContainer to)
			throws Exception
	{
		// this method needs more context in order to be more productive, but he can perform the
		// action anyways, trusting that the caller will have made the proper checks
		assert(toMove!=null && from != null && to!=null);
		assert(toMove.getParentContainer().equals(from));
		if(toMove==null || from==null || to==null)
			throw new IllegalArgumentException("null pointer parameters");
		if(!(toMove.getParentContainer().equals(from)))
			throw new Exception("from ProductContainer is not parent of toMove ProductGroup");
		
		toMove.setParentContainer(to);
		
			
	}
	
	
	/** This returns a list of all the children ProductGroups of the calling ProductContainer
	 * 
	 * @return a list of all children ProductGroups
	 */
	public List<ProductGroup> getProductGroups()
	{
		return this.groups;
	}
	
	@Override
	public int compareTo(Object o)
	{
		
		return this.getName().compareTo(((ProductContainer)o).getName());
	}	

	/**
	 * This method is used for visiting all of this productContainers 
	 * children.  The order doesn't matter here so just pass that 
	 * variable along.
	 * 
	 * @param v The visitor that is doing the visiting.
	 * @param order The order that we are currently traversing
	 * 		the tree in.
	 */
	public void traverseChildren(IVisitor v, TraversalOrder order)
	{
		for (int i = 0; i < groups.size(); i++)
		{
			groups.get(i).accept(v, order);
		}
	}
	
	/**
	 * An abstract method so that subClasses of this class
	 * can define what type of 'ProductContainer' it is. This
	 * returns a ContainerType enumerated value.
	 * 
	 * @return Returns an enumerated value of type 'ContainerType'. 
	 * 		So a StorageUnit will return type STORAGE_UNIT while
	 * 		ProductGroup will return type PRODUCT_GROUP, STORAGE_UNIT, etc. 
	 */
	public abstract ContainerType getContainerType();
}